package ru.amse.tsyganov.jumleditor.storing;

import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import ru.amse.tsyganov.jumleditor.model.activity.ActionState;
import ru.amse.tsyganov.jumleditor.model.activity.Decision;
import ru.amse.tsyganov.jumleditor.model.activity.FinalState;
import ru.amse.tsyganov.jumleditor.model.activity.ForkJoinState;
import ru.amse.tsyganov.jumleditor.model.activity.InitialState;
import ru.amse.tsyganov.jumleditor.model.activity.Transition;
import ru.amse.tsyganov.jumleditor.model.collaboration.Actor;
import ru.amse.tsyganov.jumleditor.model.collaboration.Instance;
import ru.amse.tsyganov.jumleditor.model.collaboration.Message;
import ru.amse.tsyganov.jumleditor.model.common.GraphModel;
import ru.amse.tsyganov.jumleditor.model.common.ModelElement;
import ru.amse.tsyganov.jumleditor.model.common.State;
import ru.amse.tsyganov.jumleditor.view.ViewVisitor;
import ru.amse.tsyganov.jumleditor.view.activity.ActionStateView;
import ru.amse.tsyganov.jumleditor.view.activity.DecisionView;
import ru.amse.tsyganov.jumleditor.view.activity.FinalStateView;
import ru.amse.tsyganov.jumleditor.view.activity.ForkJoinStateView;
import ru.amse.tsyganov.jumleditor.view.activity.InitialStateView;
import ru.amse.tsyganov.jumleditor.view.activity.TransitionView;
import ru.amse.tsyganov.jumleditor.view.collaboration.ActorView;
import ru.amse.tsyganov.jumleditor.view.collaboration.InstanceView;
import ru.amse.tsyganov.jumleditor.view.collaboration.MessageView;
import ru.amse.tsyganov.jumleditor.view.common.GraphView;
import ru.amse.tsyganov.jumleditor.view.common.StateView;

public class XmlWriteVisitor extends ViewVisitor {
	
	private boolean visitEdges;
	
	private final Document documentRoot;
	
	private Element graphModelElement;
	
	private Element graphViewElement;
	
	private final Element rootElement;
	
	private final Map<ModelElement, Integer> modelToIdMap;
	
	private int currentId;
	
	public XmlWriteVisitor(Document root, boolean visitTransitions) {
		
		this.documentRoot = root;
		
		this.visitEdges = visitTransitions;
		
		rootElement = documentRoot.	createElement(TagNames.ROOT);
		documentRoot.appendChild(rootElement);
		
		modelToIdMap = new HashMap<ModelElement, Integer>();
	}

	@Override
	public void visitActivityGraphView(
			final GraphView<? extends GraphModel> g) {
		
		if (modelToIdMap.containsKey(g.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(g.getModel(), id);

		graphModelElement = documentRoot.createElement(TagNames.GRAPH_MODEL);
		rootElement.appendChild(graphModelElement);
		addId(graphModelElement, id);
		
		graphViewElement = documentRoot.createElement(TagNames.GRAPH_VIEW);
		rootElement.appendChild(graphViewElement);
		addId(graphViewElement, id);
	}

	@Override
	public void visitActionStateView(ActionStateView<? extends ActionState> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.ACTION_STATE);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		modelElement.setAttribute(TagNames.ACTION_STATE_CALL_ACTION_MODE, 
				s.getModel().getCallActionMode().name());
		
		Element viewElement = documentRoot.createElement(TagNames.ACTION_STATE_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);	
		addPosition(viewElement, s);
	}

	@Override
	public void visitActorView(ActorView<? extends Actor> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.ACTOR);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		modelElement.setAttribute(TagNames.ACTOR_STEREOTYPE, s.getModel().getStereotype());
		
		Element viewElement = documentRoot.createElement(TagNames.ACTOR_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		addPosition(viewElement, s);
	}

	@Override
	public void visitInstanceView(InstanceView<? extends Instance> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.INSTANCE);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		modelElement.setAttribute(TagNames.INSTANCE_ROLE, s.getModel().getRole());
		modelElement.setAttribute(TagNames.INSTANCE_TYPE, s.getModel().getType());
		modelElement.setAttribute(TagNames.INSTANCE_IS_ACTIVE, 
				Boolean.toString(s.getModel().isActive()));
		modelElement.setAttribute(TagNames.INSTANCE_IS_MULTIPLE_INSTANCES, 
				Boolean.toString(s.getModel().isMultipleInstances()));
		
		Element viewElement = documentRoot.createElement(TagNames.INSTANCE_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		addPosition(viewElement, s);
	}

	@Override
	public void visitDecisionView(DecisionView<? extends Decision> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.DECISION);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		
		Element viewElement = documentRoot.createElement(TagNames.DECISION_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		addPosition(viewElement, s);
	}

	@Override
	public void visitFinalStateView(FinalStateView<? extends FinalState> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.FINAL_STATE);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		
		Element viewElement = documentRoot.createElement(TagNames.FINAL_STATE_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		addPosition(viewElement, s);
	}

	@Override
	public void visitForkJoinStateView(ForkJoinStateView<? extends ForkJoinState> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.FORK_JOIN_STATE);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		
		Element viewElement = documentRoot.createElement(TagNames.FORK_JOIN_STATE_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		addPosition(viewElement, s);
	}

	@Override
	public void visitInitialStateView(InitialStateView<? extends InitialState> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.INITIAL_STATE);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.NAME, s.getModel().getName());
		
		Element viewElement = documentRoot.createElement(TagNames.INITIAL_STATE_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		addPosition(viewElement, s);
	}

	@Override
	public void visitMessageView(MessageView<? extends Message> t) {
		if (!visitEdges || modelToIdMap.containsKey(t.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(t.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.MESSAGE);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		
		modelElement.setAttribute(TagNames.EDGE_SOURCE_ID, 
				modelToIdMap.get(t.getModel().getSource().getContainer()).toString());
		modelElement.setAttribute(TagNames.EDGE_TARGET_ID, 
				modelToIdMap.get(t.getModel().getTarget().getContainer()).toString());
		
		modelElement.setAttribute(TagNames.MESSAGE_OPERATION, t.getModel().getOperation());
		modelElement.setAttribute(TagNames.MESSAGE_ARGUMENT, t.getModel().getArgument());
		modelElement.setAttribute(TagNames.MESSAGE_RETURN_VALUE, t.getModel().getReturnValue());
		modelElement.setAttribute(TagNames.MESSAGE_SEQUENCE_NUMBER, t.getModel().getSequenceNumber());
		modelElement.setAttribute(TagNames.MESSAGE_SYNCHRONOUS, Boolean.toString(t.getModel().isSynchronous()));
		modelElement.setAttribute(TagNames.MESSAGE_CONDITION, t.getModel().getCondition());
		modelElement.setAttribute(TagNames.MESSAGE_ITERATION, t.getModel().getIteration());
		
		Element viewElement = documentRoot.createElement(TagNames.MESSAGE_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		
		viewElement.setAttribute(TagNames.EDGE_HORIZONTAL_SEGMENT, 
				Integer.toString(t.getBrokenLineHorizontalSegment()));
		viewElement.setAttribute(TagNames.EDGE_VERTICAL_SEGMENT, 
				Integer.toString(t.getBrokenLineVerticalSegment()));
		viewElement.setAttribute(TagNames.EDGE_SOURCE_CONN_POINT, 
				t.getSourceConnectionPoint().getNumber().toString());
		viewElement.setAttribute(TagNames.EDGE_TARGET_CONN_POINT, 
				t.getTargetConnectionPoint().getNumber().toString());
	}

	@Override
	public void visitTransitionView(TransitionView<? extends Transition> t) {
		if (!visitEdges || modelToIdMap.containsKey(t.getModel())) {
			return;
		}
		int id = currentId++;
		modelToIdMap.put(t.getModel(), id);
		
		Element modelElement = documentRoot.createElement(TagNames.TRANSITION);
		graphModelElement.appendChild(modelElement);
		addId(modelElement, id);
		modelElement.setAttribute(TagNames.EDGE_SOURCE_ID, 
				modelToIdMap.get(t.getModel().getSource().getContainer()).toString());
		modelElement.setAttribute(TagNames.EDGE_TARGET_ID, 
				modelToIdMap.get(t.getModel().getTarget().getContainer()).toString());
		
		Element viewElement = documentRoot.createElement(TagNames.TRANSITION_VIEW);
		graphViewElement.appendChild(viewElement);
		addId(viewElement, id);
		
		modelElement.setAttribute(TagNames.GUARD, Boolean.toString(t.getModel().isGuard()));
		modelElement.setAttribute(TagNames.GUARD_EXPRESSION, t.getModel().getGuardExpression());
		
		viewElement.setAttribute(TagNames.EDGE_HORIZONTAL_SEGMENT, 
				Integer.toString(t.getBrokenLineHorizontalSegment()));
		viewElement.setAttribute(TagNames.EDGE_VERTICAL_SEGMENT, 
				Integer.toString(t.getBrokenLineVerticalSegment()));
		viewElement.setAttribute(TagNames.EDGE_SOURCE_CONN_POINT, 
				t.getSourceConnectionPoint().getNumber().toString());
		viewElement.setAttribute(TagNames.EDGE_TARGET_CONN_POINT, 
				t.getTargetConnectionPoint().getNumber().toString());
	}

	
	private static void addId(Element item, int id) {
		item.setAttribute(TagNames.ID, Integer.toString(id));
	}
	
	private static void addPosition(Element item, StateView<? extends State> s) {
		item.setAttribute(TagNames.STATE_VIEW_X, Integer.toString(s.getX()));
		item.setAttribute(TagNames.STATE_VIEW_Y, Integer.toString(s.getY()));
		item.setAttribute(TagNames.STATE_VIEW_WIDTH, Integer.toString(s.getWidth()));
		item.setAttribute(TagNames.STATE_VIEW_HEIGHT, Integer.toString(s.getHeight()));
	}
	
	public boolean isVisitEdges() {
		return visitEdges;
	}

	public void setVisitTransitions(boolean visitTransitions) {
		this.visitEdges = visitTransitions;
	}
}
